"""
脚本服务实现
"""
import os
import json
import uuid
import re
from typing import Dict, Any, List, Optional, Callable

from app.services.script_service import IScriptService
from app.services.base_service import Event
from app.models.script import Script, ScriptRule
from app.utils.logger import Logger


class ScriptService(IScriptService):
    """
    脚本服务实现
    """
    
    def __init__(self):
        """
        初始化脚本服务
        """
        super().__init__()
        
        # 获取日志记录器
        self.logger = Logger.get_logger("script_service")
        
        # 脚本字典，键为脚本 ID，值为脚本对象
        self._scripts: Dict[str, Script] = {}
        
        # 脚本列表，按优先级排序
        self._script_list: List[Script] = []
        
        # 配置
        self._config: Dict[str, Any] = {
            "scripts_file": "data/scripts.json",
            "auto_save": True
        }
        
        # 是否已修改
        self._modified = False
        
        # 事件监听器字典，键为事件类型，值为监听器列表
        self._listeners: Dict[str, List[Callable[[Event], None]]] = {}
        
        self.logger.info("脚本服务初始化完成")
    
    def add_listener(self, event_type: str, listener: Callable[[Event], None]) -> None:
        """
        添加事件监听器
        
        @param event_type: 事件类型
        @param listener: 监听器函数，接收一个 Event 参数
        """
        if event_type not in self._listeners:
            self._listeners[event_type] = []
        
        if listener not in self._listeners[event_type]:
            self._listeners[event_type].append(listener)
    
    def remove_listener(self, event_type: str, listener: Callable[[Event], None]) -> bool:
        """
        移除事件监听器
        
        @param event_type: 事件类型
        @param listener: 监听器函数
        @return: 是否成功移除
        """
        if event_type in self._listeners and listener in self._listeners[event_type]:
            self._listeners[event_type].remove(listener)
            return True
        return False
    
    def clear_listeners(self, event_type: str = None) -> None:
        """
        清除事件监听器
        
        @param event_type: 事件类型，如果为 None 则清除所有监听器
        """
        if event_type is None:
            self._listeners.clear()
        elif event_type in self._listeners:
            self._listeners[event_type].clear()
    
    def notify(self, event_type: str, data: Any = None) -> None:
        """
        通知事件
        
        @param event_type: 事件类型
        @param data: 事件数据
        """
        # 创建事件对象
        event = Event(event_type, data)
        
        # 调用监听器
        if event_type in self._listeners:
            for listener in self._listeners[event_type]:
                try:
                    listener(event)
                except Exception as e:
                    # 这里可以添加日志记录
                    self.logger.error(f"事件监听器异常: {str(e)}")
    
    def load_scripts(self, file_path: str) -> bool:
        """
        从文件加载脚本
        
        @param file_path: 脚本文件路径
        @return: 是否成功加载
        """
        try:
            # 如果文件不存在，返回 True
            if not os.path.exists(file_path):
                self.logger.info(f"脚本文件不存在: {file_path}")
                return True
            
            # 读取文件
            with open(file_path, "r", encoding="utf-8") as f:
                data = json.load(f)
            
            # 清空当前脚本
            self._scripts.clear()
            self._script_list.clear()
            
            # 加载脚本
            for script_data in data:
                # 创建规则对象
                rules = []
                for rule_data in script_data.get("rules", []):
                    rule = ScriptRule(
                        rule_type=rule_data.get("rule_type", "contains"),
                        pattern=rule_data.get("pattern", ""),
                        is_regex=rule_data.get("is_regex", False)
                    )
                    rules.append(rule)
                
                # 创建脚本对象
                script = Script(
                    id=script_data.get("id", str(uuid.uuid4())),
                    name=script_data.get("name", ""),
                    rules=rules,
                    reply=script_data.get("reply", ""),
                    enabled=script_data.get("enabled", True),
                    priority=script_data.get("priority", 0),
                    timestamp=script_data.get("timestamp", ""),
                    metadata=script_data.get("metadata", {})
                )
                
                # 添加到脚本字典和列表
                self._scripts[script.id] = script
                self._script_list.append(script)
            
            # 按优先级排序
            self._sort_scripts()
            
            # 重置修改标志
            self._modified = False
            
            self.logger.info(f"已从文件加载脚本: {file_path}, 共 {len(self._scripts)} 个脚本")
            
            # 触发脚本加载事件
            self.notify("scripts_loaded", self._script_list)
            
            return True
        except Exception as e:
            self.logger.error(f"从文件加载脚本失败: {str(e)}")
            return False
    
    def save_scripts(self, file_path: str) -> bool:
        """
        保存脚本到文件
        
        @param file_path: 脚本文件路径
        @return: 是否成功保存
        """
        try:
            # 确保目录存在
            os.makedirs(os.path.dirname(file_path), exist_ok=True)
            
            # 将脚本列表转换为字典列表
            scripts_data = [script.to_dict() for script in self._script_list]
            
            # 写入文件
            with open(file_path, "w", encoding="utf-8") as f:
                json.dump(scripts_data, f, ensure_ascii=False, indent=2)
            
            # 重置修改标志
            self._modified = False
            
            self.logger.info(f"已保存脚本到文件: {file_path}")
            return True
        except Exception as e:
            self.logger.error(f"保存脚本到文件失败: {str(e)}")
            return False
    
    def add_script(self, script: Dict[str, Any]) -> str:
        """
        添加脚本
        
        @param script: 脚本数据
        @return: 脚本 ID
        """
        try:
            # 生成脚本 ID
            script_id = script.get("id", str(uuid.uuid4()))
            
            # 创建规则对象
            rules = []
            for rule_data in script.get("rules", []):
                rule = ScriptRule(
                    rule_type=rule_data.get("rule_type", "contains"),
                    pattern=rule_data.get("pattern", ""),
                    is_regex=rule_data.get("is_regex", False)
                )
                rules.append(rule)
            
            # 创建脚本对象
            script_obj = Script(
                id=script_id,
                name=script.get("name", ""),
                rules=rules,
                reply=script.get("reply", ""),
                enabled=script.get("enabled", True),
                priority=script.get("priority", 0),
                timestamp=script.get("timestamp", ""),
                metadata=script.get("metadata", {})
            )
            
            # 添加到脚本字典和列表
            self._scripts[script_id] = script_obj
            self._script_list.append(script_obj)
            
            # 按优先级排序
            self._sort_scripts()
            
            # 设置修改标志
            self._modified = True
            
            # 如果配置了自动保存，则保存脚本
            if self._config.get("auto_save", True):
                self.save_scripts(self._config.get("scripts_file", "data/scripts.json"))
            
            # 触发脚本添加事件
            self.notify("script_added", script_obj)
            
            self.logger.info(f"已添加脚本: {script_obj.name}")
            return script_id
        except Exception as e:
            self.logger.error(f"添加脚本失败: {str(e)}")
            return ""
    
    def update_script(self, script_id: str, script: Dict[str, Any]) -> bool:
        """
        更新脚本
        
        @param script_id: 脚本 ID
        @param script: 脚本数据
        @return: 是否成功更新
        """
        try:
            # 检查脚本是否存在
            if script_id not in self._scripts:
                self.logger.warning(f"脚本不存在: {script_id}")
                return False
            
            # 获取原脚本
            old_script = self._scripts[script_id]
            
            # 创建规则对象
            rules = []
            for rule_data in script.get("rules", []):
                rule = ScriptRule(
                    rule_type=rule_data.get("rule_type", "contains"),
                    pattern=rule_data.get("pattern", ""),
                    is_regex=rule_data.get("is_regex", False)
                )
                rules.append(rule)
            
            # 创建新脚本对象
            new_script = Script(
                id=script_id,
                name=script.get("name", old_script.name),
                rules=rules,
                reply=script.get("reply", old_script.reply),
                enabled=script.get("enabled", old_script.enabled),
                priority=script.get("priority", old_script.priority),
                timestamp=script.get("timestamp", old_script.timestamp),
                metadata=script.get("metadata", old_script.metadata)
            )
            
            # 更新脚本字典
            self._scripts[script_id] = new_script
            
            # 更新脚本列表
            for i, s in enumerate(self._script_list):
                if s.id == script_id:
                    self._script_list[i] = new_script
                    break
            
            # 按优先级排序
            self._sort_scripts()
            
            # 设置修改标志
            self._modified = True
            
            # 如果配置了自动保存，则保存脚本
            if self._config.get("auto_save", True):
                self.save_scripts(self._config.get("scripts_file", "data/scripts.json"))
            
            # 触发脚本更新事件
            self.notify("script_updated", new_script)
            
            self.logger.info(f"已更新脚本: {new_script.name}")
            return True
        except Exception as e:
            self.logger.error(f"更新脚本失败: {str(e)}")
            return False
    
    def delete_script(self, script_id: str) -> bool:
        """
        删除脚本
        
        @param script_id: 脚本 ID
        @return: 是否成功删除
        """
        try:
            # 检查脚本是否存在
            if script_id not in self._scripts:
                self.logger.warning(f"脚本不存在: {script_id}")
                return False
            
            # 获取脚本
            script = self._scripts[script_id]
            
            # 从脚本字典中删除
            del self._scripts[script_id]
            
            # 从脚本列表中删除
            self._script_list = [s for s in self._script_list if s.id != script_id]
            
            # 设置修改标志
            self._modified = True
            
            # 如果配置了自动保存，则保存脚本
            if self._config.get("auto_save", True):
                self.save_scripts(self._config.get("scripts_file", "data/scripts.json"))
            
            # 触发脚本删除事件
            self.notify("script_deleted", script_id)
            
            self.logger.info(f"已删除脚本: {script.name}")
            return True
        except Exception as e:
            self.logger.error(f"删除脚本失败: {str(e)}")
            return False
    
    def get_script(self, script_id: str) -> Optional[Dict[str, Any]]:
        """
        获取脚本
        
        @param script_id: 脚本 ID
        @return: 脚本数据，如果不存在则返回 None
        """
        # 检查脚本是否存在
        if script_id not in self._scripts:
            return None
        
        # 获取脚本
        script = self._scripts[script_id]
        
        # 返回脚本数据
        return script.to_dict()
    
    def get_all_scripts(self) -> List[Dict[str, Any]]:
        """
        获取所有脚本
        
        @return: 脚本列表
        """
        return [script.to_dict() for script in self._script_list]
    
    def find_matching_script(self, content: str) -> Optional[Dict[str, Any]]:
        """
        查找匹配的脚本
        
        @param content: 内容
        @return: 匹配的脚本，如果没有匹配则返回 None
        """
        # 遍历所有脚本
        for script in self._script_list:
            # 如果脚本未启用，则跳过
            if not script.enabled:
                continue
            
            # 检查是否匹配
            if self._match_script(script, content):
                # 返回脚本数据
                return script.to_dict()
        
        return None
    
    def configure(self, config: Dict[str, Any]) -> None:
        """
        配置脚本服务
        
        @param config: 配置字典
        """
        # 更新配置
        self._config.update(config)
        
        # 如果配置了脚本文件，则加载脚本
        if "scripts_file" in config:
            self.load_scripts(config["scripts_file"])
        
        self.logger.debug(f"已配置脚本服务: {config}")
    
    def shutdown(self) -> None:
        """
        关闭服务
        """
        self.logger.info("脚本服务正在关闭...")
        
        # 如果有未保存的修改，则保存脚本
        if self._modified:
            self.save_scripts(self._config.get("scripts_file", "data/scripts.json"))
        
        # 清空脚本
        self._scripts.clear()
        self._script_list.clear()
        
        # 清空监听器
        self.clear_listeners()
        
        self.logger.info("脚本服务已关闭")
    
    def _sort_scripts(self) -> None:
        """
        按优先级排序脚本列表
        """
        self._script_list.sort(key=lambda s: (-s.priority, s.name))
    
    def _match_script(self, script: Script, content: str) -> bool:
        """
        检查内容是否匹配脚本
        
        @param script: 脚本对象
        @param content: 内容
        @return: 是否匹配
        """
        # 如果没有规则，则不匹配
        if not script.rules:
            return False
        
        # 检查所有规则
        for rule in script.rules:
            # 根据规则类型进行匹配
            if rule.rule_type == "contains":
                # 包含匹配
                if rule.pattern in content:
                    return True
            elif rule.rule_type == "equals":
                # 完全匹配
                if rule.pattern == content:
                    return True
            elif rule.rule_type == "startswith":
                # 开头匹配
                if content.startswith(rule.pattern):
                    return True
            elif rule.rule_type == "endswith":
                # 结尾匹配
                if content.endswith(rule.pattern):
                    return True
            elif rule.rule_type == "regex" and rule.is_regex:
                # 正则表达式匹配
                try:
                    if re.search(rule.pattern, content):
                        return True
                except:
                    pass
        
        return False 